﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Collections;
using System.Globalization;

namespace SoftwareLicensing
{
    /// <summary>
    /// License key helper class to create/parse licenses
    /// </summary>
    public class LicenseKeyHelper
    {
        #region Private Fields and Constants

        private const int FirstRandomBytesCount = 8;
        private const int SaltBytesCount = 2;
        private const int HashBytesCount = 4;
        
        #endregion

        #region Public Methods

        /// <summary>
        /// Creates the license key.
        /// </summary>
        /// <param name="licenseKeyInfo">The license key info.</param>
        /// <returns></returns>
        public string CreateLicenseKey(LicenseKeyInfo licenseKeyInfo)
        {
            List<byte> licenseKeyBytes = new List<byte>();
            RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
            
            // append unique random bytes
            byte[] data = new byte[FirstRandomBytesCount];
            crypto.GetNonZeroBytes(data);
            licenseKeyBytes.AddRange(data);

            // append expirty time
            licenseKeyBytes.AddRange(BitConverter.GetBytes(licenseKeyInfo.ExpiryTimeUtc.Ticks));

            // append product type
            licenseKeyBytes.Add((byte)(licenseKeyInfo.ProductType));

            // append features
            licenseKeyBytes.AddRange(BitConverter.GetBytes((int)licenseKeyInfo.Features));

            // append allowed installations
            licenseKeyBytes.Add(((byte)licenseKeyInfo.AllowedInstallations));

            // append unique salt
            byte[] salt = new byte[SaltBytesCount];
            crypto.GetNonZeroBytes(salt);
            licenseKeyBytes.AddRange(salt);

            // append hash
            HashAlgorithm hash = new MD5CryptoServiceProvider();
            byte[] hashBytes = hash.ComputeHash(licenseKeyBytes.ToArray());
            licenseKeyBytes.AddRange(hashBytes.Take(HashBytesCount));

            return Base32.ToBase32String(licenseKeyBytes.ToArray());
        }

        /// <summary>
        /// Parses the license key.
        /// </summary>
        /// <param name="licenseKey">The license key.</param>
        /// <returns>Returns a non-null object is license key is valid and parsed successfully. Throse exception or returns null otherwise</returns>
        public LicenseKeyInfo ParseLicenseKey(string licenseKey)
        {
            byte[] licenseKeyBytes = Base32.FromBase32String(licenseKey);

            // validate
            byte[] hashBytes = licenseKeyBytes.Skip(licenseKeyBytes.Length-HashBytesCount).ToArray();
            byte[] nonHashBytes = licenseKeyBytes.Take(licenseKeyBytes.Length - HashBytesCount).ToArray();
            HashAlgorithm hash = new MD5CryptoServiceProvider();
            bool isValid = (hash.ComputeHash(nonHashBytes).Take(HashBytesCount).ToArray() as IStructuralEquatable).Equals(hashBytes, StructuralComparisons.StructuralEqualityComparer);
            LicenseKeyInfo licenseInfo = null;

            int dataStartIndex = 0;
            if (isValid)
            {
                // get expiry time
                dataStartIndex = FirstRandomBytesCount;
                DateTime expiryTime = DateTime.FromBinary(BitConverter.ToInt64(licenseKeyBytes, dataStartIndex));

                // get product type
                dataStartIndex = FirstRandomBytesCount + sizeof(long);
                ProductType productType = (ProductType)BitConverter.ToInt32(licenseKeyBytes, dataStartIndex);

                // get features
                dataStartIndex += sizeof(ProductType);
                ProductFeatures features = (ProductFeatures)licenseKeyBytes[dataStartIndex];

                // get allowed installations
                dataStartIndex += sizeof(ProductFeatures);
                byte allowedInstallations = licenseKeyBytes[dataStartIndex];

                licenseInfo = new LicenseKeyInfo(productType, features, expiryTime, allowedInstallations);
            }

            return licenseInfo;
        }

        #endregion

    }

}
